package com.chy.blog_server.modular.map.config;

import cn.hutool.core.util.ObjectUtil;
import com.chy.blog_server.core.util.DateUtil;
import com.chy.blog_server.core.util.StringUtil;
import com.chy.blog_server.modular.map.entity.MapLog;
import com.chy.blog_server.modular.map.entity.MapMerchantInfo;
import com.chy.blog_server.modular.map.service.impl.MapLogServiceImpl;
import com.chy.blog_server.modular.map.service.impl.MapMerchantInfoServiceImpl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * 高徳配置类
 */
public class GdConfing {

    private static final Logger logger = LoggerFactory.getLogger(GdConfing.class);

    /**
     * 高德地图API KEY组
     */
    public static final String[] GD_API_KEY_ARR = {"7acfa35031079246f81607322cb61c0e", "49e62bb0da538f0b43df3f4ab419f1ac"};

    /**
     * 高德地图API 正在使用的KEY
     */
    public static String GD_API_KEY = "7acfa35031079246f81607322cb61c0e";

    /**
     * 每页获取数量
     */
    public static final int PAGE_SIZE = 25; // 每页最大25条

    /**
     * 省份名称-行政区编码
     */
    public static final Map<String, String> provinceMap = new HashMap<>();

    static {
        provinceMap.put("河北省", "130000");
        provinceMap.put("山西省", "140000");
        provinceMap.put("辽宁省", "210000");
        provinceMap.put("吉林省", "220000");
        provinceMap.put("黑龙江省", "230000");
        provinceMap.put("江苏省", "320000");
        provinceMap.put("浙江省", "330000");
        provinceMap.put("安徽省", "340000");
        provinceMap.put("福建省", "350000");
        provinceMap.put("江西省", "360000");
        provinceMap.put("山东省", "370000");
        provinceMap.put("河南省", "410000");
        provinceMap.put("湖北省", "420000");
        provinceMap.put("湖南省", "430000");
        provinceMap.put("广东省", "440000");
        provinceMap.put("海南省", "460000");
        provinceMap.put("四川省", "510000");
        provinceMap.put("贵州省", "520000");
        provinceMap.put("云南省", "530000");
        provinceMap.put("陕西省", "610000");
        provinceMap.put("甘肃省", "620000");
        provinceMap.put("青海省", "630000");
        provinceMap.put("台湾省", "710000");
        provinceMap.put("内蒙古自治区", "150000");
        provinceMap.put("广西壮族自治区", "450000");
        provinceMap.put("西藏自治区", "540000");
        provinceMap.put("宁夏回族自治区", "640000");
        provinceMap.put("新疆维吾尔自治区", "650000");
        provinceMap.put("北京市", "110000");
        provinceMap.put("天津市", "120000");
        provinceMap.put("上海市", "310000");
        provinceMap.put("重庆市", "500000");
        provinceMap.put("香港特别行政区", "810000");
        provinceMap.put("澳门特别行政区", "820000");
    }

    /**
     * 重置KEY
     * @param mapLogService
     * @param operateIp 操作IP
     * @param logCode 日志编号
     * @param city 城市
     * @param province 省份
     * @param keyword 搜索关键词
     */
    public static void resetKey(MapLogServiceImpl mapLogService, String operateIp, String logCode, String province, String city, String keyword) throws Exception {
        try {
            //重置KEY为第一个
            GD_API_KEY = GD_API_KEY_ARR[0];
            String logText = "高德API接口调用>>>重置KEY：" + GD_API_KEY;
            logger.info(logText);
            createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 调用高德接口获取商家数据
     * @param mapLogService
     * @param operateIp 操作IP
     * @param logCode 日志编号
     * @param city 城市
     * @param province 省份
     * @param search_area 搜索的坐标行政地区名称
     * @param location 区域坐标
     * @param keyword 搜索关键词
     */
    public static List<MapMerchantInfo> getGdMerchantInfoByApi(MapLogServiceImpl mapLogService, String operateIp, String logCode, String province, String city,
                                                               String search_area, String location, String keyword) throws Exception {
        List<MapMerchantInfo> poiList = new ArrayList<>();
        try {
            // 1. 获取总页数
            int totalCount = getTotalCount(mapLogService, operateIp, logCode, province, city, search_area, location, keyword);
            int totalPage = (int) Math.ceil((double) totalCount / PAGE_SIZE);

            // 2. 分页获取所有数据
            String address = city + search_area + "(" + location + ")";
            for (int page = 1; page <= totalPage; page++) {
                List<MapMerchantInfo> pageData = queryPoiData(mapLogService, operateIp, logCode, province, city, search_area, location, keyword, page);
                if(pageData.isEmpty()){
                    String logText = "高德API接口调用>>>" + address + " 返回数据数量为0，停止调用";
                    logger.info(logText);
                    createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
                    return poiList;
                }
                poiList.addAll(pageData);
                Thread.sleep(600);
                String logText = "高德API接口调用>>>" + address + " 已获取第 " + page + " 页数据，累计：" + poiList.size();
                logger.info(logText);
                createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
            }
            String logText = "高德API接口调用完毕>>>" + address + " 总计：" + poiList.size() + "条数据";
            logger.info(logText);
            createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
        return poiList;
    }

    /**
     * 获取总记录数
     * @param mapLogService
     * @param operateIp 操作IP
     * @param logCode 日志编号
     * @param province 省份
     * @param city 城市
     * @param search_area 搜索的坐标行政地区名称
     * @param location 区域坐标
     * @param keyword 搜索关键词
     * @return
     * @throws Exception
     */
    private static int getTotalCount(MapLogServiceImpl mapLogService, String operateIp, String logCode, String province, String city,
                                     String search_area, String location, String keyword) throws Exception {
        String url = buildGdAroundKeyworkRequestUrl(city, location, keyword, 1);
        JsonNode root = sendApiRequest(url);
        //{"status":"0","info":"USER_DAILY_QUERY_OVER_LIMIT","infocode":"10044"}
        if("0".equals(root.get("status").asText())){
            String logText = "高德API接口调用>>>获取 " + city + " " + search_area + "(" + location + ")总记录数--发生错误：" + root;
            logger.info(logText);
            createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
            if("USER_DAILY_QUERY_OVER_LIMIT".equals(root.get("info").asText())){
                //当前使用的KEY已上限，切换其他KEY
                if(GD_API_KEY.equals(GD_API_KEY_ARR[1])){
                    GD_API_KEY = GD_API_KEY_ARR[0]; //全部用完则还原为第一个
                    logText = "高德API接口调用>>>获取 " + city + search_area + "(" + location + ")总记录数--发生错误：高徳周边搜索接口分配额度已用完，请明天再来！";
                    createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
                    throw new Exception(logText);
                }else{
                    logText = "高德API接口调用>>>获取总记录数--当前KEY：【" + GD_API_KEY + "】 已上限，切换KEY：【" + GD_API_KEY_ARR[1] + "】";
                    logger.info(logText);
                    createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
                    GD_API_KEY = GD_API_KEY_ARR[1];
                    return getTotalCount(mapLogService, operateIp, logCode, province, city, search_area, location, keyword);
                }
            } else {
                logText = "高德API接口调用>>>获取 " + city + search_area + "(" + location + ")总记录数--发生错误：" + root.get("info").asText();
                createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
                throw new Exception(logText);
            }
        }else{
            String logText = "高德API接口调用>>>获取 " + city + search_area + " (" + location + ") 总记录数：" + root.get("count").asInt();
            logger.info(logText);
            createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
            return root.get("count").asInt();
        }
    }

    /**
     * 分页查询数据
     * @param mapLogService
     * @param operateIp 操作IP
     * @param logCode 日志编号
     * @param province 省份
     * @param city 城市
     * @param search_area 搜索的坐标行政地区名称
     * @param location 区域坐标
     * @param keyword 区域坐标
     * @param page
     * @return
     * @throws Exception
     */
    private static List<MapMerchantInfo> queryPoiData(MapLogServiceImpl mapLogService, String operateIp, String logCode, String province, String city,
                                                      String search_area, String location, String keyword, int page) throws Exception {
        //构建高德url
        String url = buildGdAroundKeyworkRequestUrl(city, location, keyword, page);
        //调用接口
        JsonNode dataJson = sendApiRequest(url);
        if("0".equals(dataJson.get("status").asText())){
            if("USER_DAILY_QUERY_OVER_LIMIT".equals(dataJson.get("info").asText())){
                //当前使用的KEY已上限，切换其他KEY
                if(GD_API_KEY.equals(GD_API_KEY_ARR[1])){
                    GD_API_KEY = GD_API_KEY_ARR[0]; //全部用完则还原为第一个
                    String logText = "高德API接口调用>>>获取 " + city + search_area + "(" + location + ")数据--发生错误：高徳周边搜索接口分配额度已用完！请明天再来";
                    createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
                    throw new Exception(logText);
                }else{
                    String logText = "高德API接口调用>>>分页查询数据--当前KEY：" + GD_API_KEY + " 已上限，切换KEY：" + GD_API_KEY_ARR[1];
                    logger.info(logText);
                    createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
                    GD_API_KEY = GD_API_KEY_ARR[1];
                    return queryPoiData(mapLogService, operateIp, logCode, province, city, search_area, location, keyword, page);
                }
            }else{
                String logText = "高德API接口调用>>>获取 " + city + search_area + "(" + location + ")数据--发生错误：" + dataJson.get("info");
                createGaodeLog(mapLogService, logCode, logText, province, city, keyword, operateIp); //生成日志
                throw new Exception(logText);
            }
        }
        return parsePoiList(dataJson, keyword, city, search_area, location);
    }

    /**
     * 获取高德区/县坐标
     * @param province 省份
     * @param city 城市
     * @param area 区/县
     * @return
     */
    private static String getGdLocation(String province, String city, String area) throws Exception {
        //构建url
        String url = buildGdLocationRequestUrl(province, city, area);
        JsonNode root = sendApiRequest(url);
        if("0".equals(root.get("status").asText())){
            logger.info("高德API接口调用>>>获取高德区/县坐标--发生错误：{}", root);
            throw new Exception(root.get("msg").asText());
        } else if(root.get("count").asInt() == 0){
            String error = "高德API接口调用>>>获取高德区/县坐标--没有获取到区县坐标，请检查省份、城市、区县是否正确";
            logger.info(error);
            throw new Exception(error);
        }else if(root.get("count").asInt() > 1){
            String error = "高德API接口调用>>>获取高德区/县坐标--存在多个坐标：" + root.get("count").asText();
            logger.info(error);
            throw new Exception(error);
        } else if("1".equals(root.get("count").asText())){
            JsonNode geocodes = root.get("geocodes");
            String location = geocodes.get(0).get("location").asText();
            logger.info("高德API接口调用>>>获取高德区/县坐标：{}", location);
            return location;
        }
        return null;
    }

    /**
     * 获取高德省份城市对应的全部区/县坐标
     * @param province 省份
     * @param city 城市
     * @param area 区/县
     * @return
     */
    private static List<String> getGdAllAreaLocation(String province, String city, String area) throws Exception {
        List<String> locationList = new ArrayList<>();
        //行政区编码
        String pCode = provinceMap.get(province);
        //构建url
        String url = "https://restapi.amap.com/v3/config/district?" +
                "keywords=" + pCode +
                "&subdistrict=3" +
                "&extensions=base" +
                "&key=" + GD_API_KEY;
        JsonNode root = sendApiRequest(url);
        if("0".equals(root.get("status").asText())){
            logger.info("高德API接口调用>>>获取高德省份城市对应的全部区/县坐标--发生错误：{}", root);
            throw new Exception(root.get("msg").asText());
        } else if(root.get("count").asInt() == 0){
            String error = "高德API接口调用>>>获取高德省份城市对应的全部区/县坐标--没有获取到省份坐标，请检查省份是否正确";
            logger.info(error);
            throw new Exception(error);
        }else if(root.get("count").asInt() > 1){
            String error = "高德API接口调用>>>获取高德省份城市对应的全部区/县坐标--存在多个省份坐标：" + root.get("count").asText();
            logger.info(error);
            throw new Exception(error);
        } else if("1".equals(root.get("count").asText())){
            JsonNode districts = root.get("districts");
            if(districts != null && !"[]".equals(districts.toString())){
                JsonNode districtNode = districts.get(0); //省
                JsonNode cityNode= districtNode.get("districts"); //城市
                if(cityNode == null || "[]".equals(cityNode.toString())){ //没有城市则返回省坐标
                    locationList.add(districtNode.get("center").asText());
                }else{
                    //特别行政区（香港特别行政区810000、澳门特别行政区820000）
                    if("810000".equals(pCode) || "820000".equals(pCode)){
                        cityNode.forEach(node -> {
                            locationList.add(node.get("center").asText());
                        });
                        //直辖市（北京市110000、天津市120000、上海市310000、重庆市500000）
                    }if("110000".equals(pCode) || "120000".equals(pCode) || "310000".equals(pCode) || "500000".equals(pCode)){
                        cityNode.forEach(node -> {
                            JsonNode areaNode= node.get("districts"); //城市
                            if(areaNode == null || "[]".equals(areaNode.toString())){ //没有区县则返回城市坐标
                                locationList.add(node.get("center").asText());
                            }else{
                                areaNode.forEach(an -> {
                                    locationList.add(an.get("center").asText());
                                });
                            }
                        });
                        //省
                    }else{
                        cityNode.forEach(node -> {
                            if(city.equals(node.get("name").asText())){
                                JsonNode areaNode= node.get("districts"); //城市
                                if(areaNode == null){ //没有区县则返回城市坐标
                                    locationList.add(node.get("center").asText());
                                }else{
                                    areaNode.forEach(an -> {
                                        locationList.add(an.get("center").asText());
                                    });
                                }
                            }
                        });
                    }
                }
            }else{
                String error = "高德API接口调用>>>获取高德省份城市对应的全部区/县坐标--该省份没有城市：" + root.get("count").asText();
                logger.info(error);
                throw new Exception(error);
            }
        }
        return locationList;
    }

    /**
     * 构建高德区/县坐标url
     * @param province 省份
     * @param city 城市
     * @param area 区/县
     * @return
     */
    private static String buildGdLocationRequestUrl(String province, String city, String area) {
        return "https://restapi.amap.com/v3/geocode/geo?" +
                "city=" + city +
                "&address=" + province + city + area +
                "&key=" + GD_API_KEY;
    }

    /**
     * 构建高德关键词url
     * @param city
     * @param keyword
     * @param page
     * @return
     */
    private static String buildGdKeyworkRequestUrl(String city, String keyword, int page) {
        //1.0
        return "https://restapi.amap.com/v3/place/text?" +
                "keywords=" + keyword +
                "&city=" + city +
                "&citylimit=true" +  //只返回搜索城市
                "&offset=" + PAGE_SIZE +
                "&page=" + page +
                "&extensions=all" + //返回结果控制,默认base
                "&key=" + GD_API_KEY;
    }

    /**
     * 构建高德周边坐标+关键词url
     * @param city
     * @param keyword
     * @param page
     * @return
     */
    private static String buildGdAroundKeyworkRequestUrl(String city, String location, String keyword, int page) {
        //1.0
        return "https://restapi.amap.com/v3/place/around?" +
                "location=" + location +
                "&keywords=" + keyword +
                "&city=" + city +
                "&radius=" + 7000 + //查询半径:0-50000米
                "&offset=" + PAGE_SIZE +
                "&page=" + page +
                "&extensions=all" + //返回结果控制,默认base
                "&key=" + GD_API_KEY;
    }

    /**
     * 构建高德行政地区url-全国
     * @return
     */
    public static String buildGdDistrictRequestUrl() {
        return "https://restapi.amap.com/v3/config/district?" +
                "keywords=100000" +
                "&subdistrict=3" +
                "&extensions=base" +
                "&key=" + GD_API_KEY;
    }

    /**
     * 发送API请求并解析JSON
     * @param url
     * @return
     * @throws Exception
     */
    public static JsonNode sendApiRequest(String url) throws Exception {
        CloseableHttpClient client = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);
        CloseableHttpResponse response = client.execute(httpGet);
        String json = EntityUtils.toString(response.getEntity());
        return new ObjectMapper().readTree(json);
    }

    /**
     * 解析数据列表
     * @param root
     * @param keyword 搜索关键词
     * @param city 城市
     * @param search_area 搜索的坐标行政地区名称
     * @param location 区域坐标
     * @return
     */
    private static List<MapMerchantInfo> parsePoiList(JsonNode root, String keyword, String city, String search_area, String location) throws Exception {
        if("0".equals(root.get("status").asText())){
            logger.info("高德API接口调用>>>发生错误：{}", root);
            throw new Exception("高德API接口调用>>>发生错误：" + root.get("msg").asText());
        }
        String searchArea = search_area + "(" + location + ")";
        List<MapMerchantInfo> list = new ArrayList<>();
        JsonNode pois = root.get("pois");
        pois.forEach(node -> {
            String pname = node.get("pname").asText(); //省份
//            String city = node.get("cityname").asText(); //城市(使用搜索的，因有些市区分城区和郊区，接口返回的可能只是城市名称，导致查询数据不对)
            String adname = node.get("adname").asText(); //区/县
            String area = "";
            //有些城市没有区，只有街道时，区/县=城市，则存入搜索的行政地区名称
            if(city.equals(adname)){
                area = search_area;
            }else{
                area = adname;
            }

            //信息最后更新时间
            String timestamp = "";
            if(ObjectUtil.isNotNull(node.get("timestamp"))){
                timestamp = node.get("timestamp").asText();
            }

            MapMerchantInfo mapMerchantInfo = new MapMerchantInfo();
            mapMerchantInfo.preInsertNoUser();
            mapMerchantInfo.setName(node.get("name").asText());
            mapMerchantInfo.setAddress(pname + city + adname + node.get("address").asText("无地址信息"));
            mapMerchantInfo.setTel(node.get("tel").asText("暂无电话"));
            if(StringUtil.notNull(timestamp)){
                mapMerchantInfo.setInfoUpdateTime(DateUtil.getDateUtileString(timestamp));
            }
            mapMerchantInfo.setLocation(node.get("location").asText());
            mapMerchantInfo.setProvince(pname);
            mapMerchantInfo.setCity(city);
            mapMerchantInfo.setArea(area);
            mapMerchantInfo.setSearchArea(searchArea);
            mapMerchantInfo.setKeyword(keyword);
            mapMerchantInfo.setType(node.get("type").asText());
            mapMerchantInfo.setDataId(node.get("id").asText());
            mapMerchantInfo.setDataSource("gaode");
            list.add(mapMerchantInfo);
        });
        return list;
    }

    /**
     * 生成高徳日志
     * @param mapLogService
     * @param logCode
     * @param logText
     * @param province
     * @param city
     * @param keywork
     */
    public static void createGaodeLog(MapLogServiceImpl mapLogService, String logCode, String logText, String province, String city, String keywork, String operateIp){
        MapLog mapLog = mapLogService.createLogInfo(logCode, "gaode", "1", province, city, keywork, 0, 0, 0, logText, operateIp);
        mapLogService.save(mapLog);
    }

}
